/*
Designer   : Renyangang

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package soc

import (
	"context"
	"fpga-vboard/device"
	"fpga-vboard/logger"
	"fpga-vboard/pins"
	"fpga-vboard/timer"
	"time"

	"github.com/wailsapp/wails/v2/pkg/runtime"
)

type SocConfig struct {
	Name       string
	PinMaps    []*pins.PinMap
	Global     *device.SocGlobalConfig
	CPU        *device.CPUConfig
	RealTimer  *device.RealTimerConfig
	Ram        *device.RamConfig
	Flash      *device.FlashConfig
	UartDiaply *device.UartDisplayConfig
	Leds       *device.LedsConfig
	Switchs    *device.SwitchsConfig
	NixieTube  *device.NixieTubeConfig
}

type Soc struct {
	Name         string
	Devices      []device.Device
	GlobalRunner *device.SocGlobal
	IsRunning    bool
}

func NewSoc(name string, devices []device.Device, global *device.SocGlobal) *Soc {
	return &Soc{
		Name:         name,
		Devices:      devices,
		IsRunning:    false,
		GlobalRunner: global,
	}
}

func (soc *Soc) AddDev(dev device.Device) {
	if dev == nil {
		return
	}
	soc.Devices = append(soc.Devices, dev)
}

func LoadSoc(config *SocConfig) *Soc {
	config.Global.Self = config.Global

	// 可能从文件加载丢失了实例指向，这里恢复
	config.Flash.Self = config.Flash
	config.Ram.Self = config.Ram
	config.CPU.Self = config.CPU
	config.RealTimer.Self = config.RealTimer
	config.UartDiaply.Self = config.UartDiaply
	config.Leds.Self = config.Leds
	config.Switchs.Self = config.Switchs
	config.NixieTube.Self = config.NixieTube

	pins.SetMappingPins(config.PinMaps)

	global := config.Global.MakeDevice().(*device.SocGlobal)
	soc := NewSoc(config.Name, []device.Device{}, global)
	soc.AddDev(config.Flash.MakeDevice())
	soc.AddDev(config.Ram.MakeDevice())
	soc.AddDev(config.RealTimer.MakeDevice())
	soc.AddDev(config.UartDiaply.MakeDevice())
	soc.AddDev(config.Leds.MakeDevice())
	soc.AddDev(config.Switchs.MakeDevice())
	soc.AddDev(config.NixieTube.MakeDevice())
	soc.AddDev(config.CPU.MakeDevice())

	return soc
}

func (soc *Soc) Run(ctx context.Context) {
	if !soc.IsRunning {
		logger.InfoLog("dev[%v] is starting...", soc.Name)
		err := pins.MakeFullPinMapping()
		if err != nil {
			logger.FatalEvent("dev[%v] make full pin mapping failed. %v", soc.Name, err)
			return
		}
		soc.GlobalRunner.Start(ctx) // must before devices start
		for _, dev := range soc.Devices {
			err := dev.Start(ctx)
			if err != nil {
				logger.FatalEvent("dev[%v] start failed. %v", dev.Info(), err)
				return
			}
		}
		soc.IsRunning = true
		interval := int64(time.Second / time.Duration(soc.GlobalRunner.ClockFrequency*2))
		runtime.EventsEmit(ctx, "start", soc.Name)
		logger.InfoLog("dev[%v] start success, interval=%v", soc.Name, interval)
		timer.SetNanoSleepInterval(interval)
		for soc.IsRunning {
			if !soc.IsRunning {
				soc.GlobalRunner.Stop(ctx)
				break
			}
			soc.GlobalRunner.Run(ctx)
			for _, dev := range soc.Devices {
				dev.Run(ctx)
			}
			timer.NanosleepC(interval)
		}
		for _, dev := range soc.Devices {
			dev.Stop(ctx)
		}
		runtime.EventsEmit(ctx, "stop", soc.Name)
		soc.Devices = []device.Device{}
		pins.ClearPins()
		logger.InfoLog("dev[%v] stop success", soc.Name)
	}
}

func (soc *Soc) Stop() {
	if soc.IsRunning {
		soc.IsRunning = false
	}
}

func (socConfig *SocConfig) InitDevFrontConfig() {
	device.InitDevFrontendConfig(socConfig.Global)
	device.InitDevFrontendConfig(socConfig.CPU)
	device.InitDevFrontendConfig(socConfig.RealTimer)
	device.InitDevFrontendConfig(socConfig.Ram)
	device.InitDevFrontendConfig(socConfig.Flash)
	device.InitDevFrontendConfig(socConfig.UartDiaply)
	device.InitDevFrontendConfig(socConfig.Leds)
	device.InitDevFrontendConfig(socConfig.Switchs)
	device.InitDevFrontendConfig(socConfig.NixieTube)
}

func GetDefaultSocConfig() *SocConfig {
	config := &SocConfig{
		Name:       "default",
		Global:     device.GetDevsByType(device.SOC_GLOBAL)[0].(*device.SocGlobalConfig),
		CPU:        device.GetDevsByType(device.CPU)[0].(*device.CPUConfig),
		RealTimer:  device.GetDevsByType(device.RTC)[0].(*device.RealTimerConfig),
		Ram:        device.GetDevsByType(device.SDRAM)[0].(*device.RamConfig),
		Flash:      device.GetDevsByType(device.FLASH)[0].(*device.FlashConfig),
		UartDiaply: device.GetDevsByType(device.UartDiaply)[0].(*device.UartDisplayConfig),
		Leds:       device.GetDevsByType(device.Leds)[0].(*device.LedsConfig),
		Switchs:    device.GetDevsByType(device.Switchs)[0].(*device.SwitchsConfig),
		NixieTube:  device.GetDevsByType(device.NixieTube)[0].(*device.NixieTubeConfig),
	}

	config.InitDevFrontConfig()
	return config
}
